

<!DOCTYPE html>
<html lang="zh-CN" data-default-color-scheme=auto>



<head>
  <meta charset="UTF-8">
  <link rel="apple-touch-icon" sizes="76x76" href="https://img.mkerosene.cn/touxiang.jpg">
  <link rel="icon" href="https://img.mkerosene.cn/touxiang.jpg">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, shrink-to-fit=no">
  <meta http-equiv="x-ua-compatible" content="ie=edge">
  
    <meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
  
  <meta name="theme-color" content="#2f4154">
  <meta name="author" content="Kerosene W">
  <meta name="keywords" content="">
  
    <meta name="description" content="Shell 脚本​        到目前为止，我们已经学习来如何在shell中执行命令，并使用管道将命令组合使用。但是，很多情况下我们需要执行一系列的操作并使用条件或循环这样的控制流。 shell脚本是一种更加复杂度的工具。 ​        大多数shell都有自己的一套脚本语言，包括变量、控制流和自己的语法。shell脚本与其他脚本语言不同之处在于，shell脚本针对shell所从事的相关工作">
<meta property="og:type" content="article">
<meta property="og:title" content="计算机教育中缺失的一课(二)">
<meta property="og:url" content="http://example.com/2021/10/27/%E8%AE%A1%E7%AE%97%E6%9C%BA%E6%95%99%E8%82%B2%E4%B8%AD%E7%BC%BA%E5%A4%B1%E7%9A%84%E4%B8%80%E8%AF%BE(%E4%BA%8C)/index.html">
<meta property="og:site_name" content="追求源于热爱">
<meta property="og:description" content="Shell 脚本​        到目前为止，我们已经学习来如何在shell中执行命令，并使用管道将命令组合使用。但是，很多情况下我们需要执行一系列的操作并使用条件或循环这样的控制流。 shell脚本是一种更加复杂度的工具。 ​        大多数shell都有自己的一套脚本语言，包括变量、控制流和自己的语法。shell脚本与其他脚本语言不同之处在于，shell脚本针对shell所从事的相关工作">
<meta property="og:locale" content="zh_CN">
<meta property="article:published_time" content="2021-10-27T12:52:11.000Z">
<meta property="article:modified_time" content="2021-10-28T12:27:54.629Z">
<meta property="article:author" content="Kerosene W">
<meta property="article:tag" content="Linux">
<meta property="article:tag" content="missing-semester">
<meta name="twitter:card" content="summary_large_image">
  
  
  <title>计算机教育中缺失的一课(二) - 追求源于热爱</title>

  <link  rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4/dist/css/bootstrap.min.css" />


  <link  rel="stylesheet" href="https://cdn.jsdelivr.net/npm/github-markdown-css@4/github-markdown.min.css" />
  <link  rel="stylesheet" href="/lib/hint/hint.min.css" />

  
    
    
      
      <link  rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@10/styles/github-gist.min.css" />
    
  

  
    <link  rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@3/dist/jquery.fancybox.min.css" />
  


<!-- 主题依赖的图标库，不要自行修改 -->

<link rel="stylesheet" href="//at.alicdn.com/t/font_1749284_ba1fz6golrf.css">



<link rel="stylesheet" href="//at.alicdn.com/t/font_1736178_kmeydafke9r.css">


<link  rel="stylesheet" href="/css/main.css" />

<!-- 自定义样式保持在最底部 -->


  <script id="fluid-configs">
    var Fluid = window.Fluid || {};
    var CONFIG = {"hostname":"example.com","root":"/","version":"1.8.13","typing":{"enable":true,"typeSpeed":70,"cursorChar":"_","loop":false},"anchorjs":{"enable":true,"element":"h1,h2,h3,h4,h5,h6","placement":"right","visible":"hover","icon":"❡"},"progressbar":{"enable":true,"height_px":3,"color":"#29d","options":{"showSpinner":false,"trickleSpeed":100}},"copy_btn":true,"image_zoom":{"enable":true,"img_url_replace":["",""]},"toc":{"enable":true,"headingSelector":"h1,h2,h3,h4,h5,h6","collapseDepth":0},"lazyload":{"enable":true,"loading_img":"https://img.mkerosene.cn/loading.gif","onlypost":false,"offset_factor":2},"web_analytics":{"enable":true,"baidu":null,"google":null,"gtag":null,"tencent":{"sid":null,"cid":null},"woyaola":null,"cnzz":null,"leancloud":{"app_id":"FI1uAFAYz0Q3MPDiORqd0JAN-gzGzoHsz","app_key":"EhKAjMe3bmq0WLTSgktGg2OC","server_url":null,"path":"window.location.pathname","ignore_local":false}},"search_path":"/local-search.xml"};
  </script>
  <script  src="/js/utils.js" ></script>
  <script  src="/js/color-schema.js" ></script>
</head>


<body>
  <header style="height: 70vh;">
    <nav id="navbar" class="navbar fixed-top  navbar-expand-lg navbar-dark scrolling-navbar">
  <div class="container">
    <a class="navbar-brand" href="/">
      <strong>mKerosene</strong>
    </a>

    <button id="navbar-toggler-btn" class="navbar-toggler" type="button" data-toggle="collapse"
            data-target="#navbarSupportedContent"
            aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <div class="animated-icon"><span></span><span></span><span></span></div>
    </button>

    <!-- Collapsible content -->
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav ml-auto text-center">
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/">
                <i class="iconfont icon-home-fill"></i>
                首页
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/archives/">
                <i class="iconfont icon-archive-fill"></i>
                归档
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/categories/">
                <i class="iconfont icon-category-fill"></i>
                分类
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/tags/">
                <i class="iconfont icon-tags-fill"></i>
                标签
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/links/">
                <i class="iconfont icon-link-fill"></i>
                友链
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/about/">
                <i class="iconfont icon-user-fill"></i>
                关于
              </a>
            </li>
          
        
        
          <li class="nav-item" id="search-btn">
            <a class="nav-link" target="_self" href="javascript:;" data-toggle="modal" data-target="#modalSearch" aria-label="Search">
              &nbsp;<i class="iconfont icon-search"></i>&nbsp;
            </a>
          </li>
        
        
          <li class="nav-item" id="color-toggle-btn">
            <a class="nav-link" target="_self" href="javascript:;" aria-label="Color Toggle">&nbsp;<i
                class="iconfont icon-dark" id="color-toggle-icon"></i>&nbsp;</a>
          </li>
        
      </ul>
    </div>
  </div>
</nav>

    <div class="banner" id="banner" parallax=true
         style="background: url('https://img.mkerosene.cn/default.png') no-repeat center center;
           background-size: cover;">
      <div class="full-bg-img">
        <div class="mask flex-center" style="background-color: rgba(0, 0, 0, 0.3)">
          <div class="page-header text-center fade-in-up">
            <span class="h2" id="subtitle" title="计算机教育中缺失的一课(二)">
              
            </span>

            
              <div class="mt-3">
  
  
    <span class="post-meta">
      <i class="iconfont icon-date-fill" aria-hidden="true"></i>
      <time datetime="2021-10-27 20:52" pubdate>
        2021年10月27日 晚上
      </time>
    </span>
  
</div>

<div class="mt-1">
  
    <span class="post-meta mr-2">
      <i class="iconfont icon-chart"></i>
      7.4k 字
    </span>
  

  
    <span class="post-meta mr-2">
      <i class="iconfont icon-clock-fill"></i>
      
      
      24 分钟
    </span>
  

  
  
    
      <!-- LeanCloud 统计文章PV -->
      <span id="leancloud-page-views-container" class="post-meta" style="display: none">
        <i class="iconfont icon-eye" aria-hidden="true"></i>
        <span id="leancloud-page-views"></span> 次
      </span>
    
  
</div>

            
          </div>

          
        </div>
      </div>
    </div>
  </header>

  <main>
    
      

<div class="container-fluid nopadding-x">
  <div class="row nomargin-x">
    <div class="d-none d-lg-block col-lg-2"></div>
    <div class="col-lg-8 nopadding-x-md">
      <div class="container nopadding-x-md" id="board-ctn">
        <div class="py-5" id="board">
          <article class="post-content mx-auto">
            <!-- SEO header -->
            <h1 style="display: none">计算机教育中缺失的一课(二)</h1>
            
              <p class="note note-info">
                
                  本文最后更新于：1 年前
                
              </p>
            
            <div class="markdown-body">
              <h2 id="Shell-脚本"><a href="#Shell-脚本" class="headerlink" title="Shell 脚本"></a>Shell 脚本</h2><p>​        到目前为止，我们已经学习来如何在shell中执行命令，并使用管道将命令组合使用。但是，很多情况下我们需要执行一系列的操作并使用条件或循环这样的控制流。</p>
<p>shell脚本是一种更加复杂度的工具。</p>
<p>​        大多数shell都有自己的一套脚本语言，包括变量、控制流和自己的语法。shell脚本与其他脚本语言不同之处在于，shell脚本针对shell所从事的相关工作进行来优化。因此，创建命令流程（pipelines）、将结果保存到文件、从标准输入中读取输入，这些都是shell脚本中的原生操作，这让它比通用的脚本语言更易用。本节中，我们会专注于bash脚本，因为它最流行，应用更为广泛。</p>
<p>​        在bash中为变量赋值的语法是<code>foo=bar</code>，访问变量中存储的数值，其语法为 <code>$foo</code>。 需要注意的是，<code>foo = bar</code> （使用空格隔开）是不能正确工作的，因为解释器会调用程序<code>foo</code> 并将 <code>=</code> 和 <code>bar</code>作为参数。 总的来说，在shell脚本中使用空格会起到分割参数的作用，有时候可能会造成混淆，请务必多加检查。</p>
<p>​        Bash中的字符串通过<code>&#39;</code> 和 <code>&quot;</code>分隔符来定义，但是它们的含义并不相同。以<code>&#39;</code>定义的字符串为原义字符串，其中的变量不会被转义，而 <code>&quot;</code>定义的字符串会将变量值进行替换。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs bash">foo=bar<br><span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;<span class="hljs-variable">$foo</span>&quot;</span><br><span class="hljs-comment"># 打印 bar</span><br><span class="hljs-built_in">echo</span> <span class="hljs-string">&#x27;$foo&#x27;</span><br><span class="hljs-comment"># 打印 $foo</span><br></code></pre></td></tr></table></figure>

<p>​        和其他大多数的编程语言一样，<code>bash</code>也支持<code>if</code>, <code>case</code>, <code>while</code> 和 <code>for</code> 这些控制流关键字。同样地, <code>bash</code> 也支持函数，它可以接受参数并基于参数进行操作。下面这个函数是一个例子，它会创建一个文件夹并使用<code>cd</code>进入该文件夹。</p>
<figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs nginx"><span class="hljs-attribute">mcd</span> () &#123;<br>    <span class="hljs-attribute">mkdir</span> -p <span class="hljs-string">&quot;<span class="hljs-variable">$1</span>&quot;</span><br>    cd <span class="hljs-string">&quot;<span class="hljs-variable">$1</span>&quot;</span><br>&#125;<br></code></pre></td></tr></table></figure>

<p>​        这里 <code>$1</code> 是脚本的第一个参数。与其他脚本语言不同的是，bash使用了很多特殊的变量来表示参数、错误代码和相关变量。下面是列举来其中一些变量，更完整的列表可以参考 <a target="_blank" rel="noopener" href="https://www.tldp.org/LDP/abs/html/special-chars.html">这里</a>。</p>
<ul>
<li><code>$0</code> - 脚本名</li>
<li><code>$1</code> 到 <code>$9</code> - 脚本的参数。 <code>$1</code> 是第一个参数，依此类推。</li>
<li><code>$@</code> - 所有参数</li>
<li><code>$#</code> - 参数个数</li>
<li><code>$?</code> - 前一个命令的返回值</li>
<li><code>$$</code> - 当前脚本的进程识别码</li>
<li><code>!!</code> - 完整的上一条命令，包括参数。常见应用：当你因为权限不足执行命令失败时，可以使用 <code>sudo !!</code>再尝试一次。</li>
<li><code>$_</code> - 上一条命令的最后一个参数。如果你正在使用的是交互式shell，你可以通过按下 <code>Esc</code> 之后键入 . 来获取这个值。</li>
</ul>
<p>​        命令通常使用 <code>STDOUT</code>来返回输出值，使用<code>STDERR</code> 来返回错误及错误码，便于脚本以更加友好的方式报告错误。 返回码或退出状态是脚本/命令之间交流执行状态的方式。返回值0表示正常执行，其他所有非0的返回值都表示有错误发生。</p>
<p>​        退出码可以搭配<code>&amp;&amp;</code> (与操作符) 和 <code>||</code> (或操作符)使用，用来进行条件判断，决定是否执行其他程序。它们都属于短路<a target="_blank" rel="noopener" href="https://en.wikipedia.org/wiki/Short-circuit_evaluation">运算符</a>（short-circuiting） 同一行的多个命令可以用<code>;</code>分隔。程序 <code>true</code> 的返回码永远是<code>0</code>，<code>false</code> 的返回码永远是<code>1</code>。让我们看几个例子</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-literal">false</span> || <span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;Oops, fail&quot;</span><br><span class="hljs-comment"># Oops, fail</span><br><br><span class="hljs-literal">true</span> || <span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;Will not be printed&quot;</span><br><span class="hljs-comment">#</span><br><br><span class="hljs-literal">true</span> &amp;&amp; <span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;Things went well&quot;</span><br><span class="hljs-comment"># Things went well</span><br><br><span class="hljs-literal">false</span> &amp;&amp; <span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;Will not be printed&quot;</span><br><span class="hljs-comment">#</span><br><br><span class="hljs-literal">false</span> ; <span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;This will always run&quot;</span><br><span class="hljs-comment"># This will always run</span><br></code></pre></td></tr></table></figure>

<p>另一个常见的模式是以变量的形式获取一个命令的输出，这可以通过 <em>命令替换</em> (<em>command substitution</em>)实现。</p>
<p>​        当您通过 <code>$( CMD )</code> 这样的方式来执行<code>CMD</code> 这个命令时，它的输出结果会替换掉 <code>$( CMD )</code> 。例如，如果执行 <code>for file in $(ls)</code> ，shell首先将调用<code>ls</code> ，然后遍历得到的这些返回值。还有一个冷门的类似特性是 <em>进程替换</em>（<em>process substitution</em>）， <code>&lt;( CMD )</code> 会执行 <code>CMD</code> 并将结果输出到一个临时文件中，并将 <code>&lt;( CMD )</code> 替换成临时文件名。这在我们希望返回值通过文件而不是STDIN传递时很有用。例如， <code>diff &lt;(ls foo) &lt;(ls bar)</code> 会显示文件夹 <code>foo</code> 和 <code>bar</code> 中文件的区别。</p>
<p>说了很多，现在该看例子了，下面这个例子展示了一部分上面提到的特性。这段脚本会遍历我们提供的参数，使用<code>grep</code> 搜索字符串 <code>foobar</code>，如果没有找到，则将其作为注释追加到文件中。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-meta">#!/bin/bash</span><br><br><span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;Starting program at <span class="hljs-subst">$(date)</span>&quot;</span> <span class="hljs-comment"># date会被替换成日期和时间</span><br><br><span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;Running program <span class="hljs-variable">$0</span> with <span class="hljs-variable">$#</span> arguments with pid $$&quot;</span><br><br><span class="hljs-keyword">for</span> file <span class="hljs-keyword">in</span> <span class="hljs-string">&quot;<span class="hljs-variable">$@</span>&quot;</span>; <span class="hljs-keyword">do</span><br>    grep foobar <span class="hljs-string">&quot;<span class="hljs-variable">$file</span>&quot;</span> &gt; /dev/null 2&gt; /dev/null<br>    <span class="hljs-comment"># 如果模式没有找到，则grep退出状态为 1</span><br>    <span class="hljs-comment"># 我们将标准输出流和标准错误流重定向到Null，因为我们并不关心这些信息</span><br>    <span class="hljs-keyword">if</span> [[ $? -ne 0 ]]; <span class="hljs-keyword">then</span><br>        <span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;File <span class="hljs-variable">$file</span> does not have any foobar, adding one&quot;</span><br>        <span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;# foobar&quot;</span> &gt;&gt; <span class="hljs-string">&quot;<span class="hljs-variable">$file</span>&quot;</span><br>    <span class="hljs-keyword">fi</span><br><span class="hljs-keyword">done</span><br></code></pre></td></tr></table></figure>

<p>​        在条件语句中，我们比较 <code>$?</code> 是否等于0。 Bash实现了许多类似的比较操作，您可以查看 <a target="_blank" rel="noopener" href="https://man7.org/linux/man-pages/man1/test.1.html"><code>test 手册</code></a>。 在bash中进行比较时，尽量使用双方括号 <code>[[ ]]</code> 而不是单方括号 <code>[ ]</code>，这样会降低犯错的几率，尽管这样并不能兼容 <code>sh</code>。 更详细的说明参见<a target="_blank" rel="noopener" href="http://mywiki.wooledge.org/BashFAQ/031">这里</a>。</p>
<p>​        当执行脚本时，我们经常需要提供形式类似的参数。bash使我们可以轻松的实现这一操作，它可以基于文件扩展名展开表达式。这一技术被称为shell的 <em>通配</em>（ <em>globbing</em>）</p>
<ul>
<li>通配符 - 当你想要利用通配符进行匹配时，你可以分别使用 <code>?</code> 和 <code>*</code> 来匹配一个或任意个字符。例如，对于文件<code>foo</code>, <code>foo1</code>, <code>foo2</code>, <code>foo10</code> 和 <code>bar</code>, <code>rm foo?</code>这条命令会删除<code>foo1</code> 和 <code>foo2</code> ，而<code>rm foo*</code> 则会删除除了<code>bar</code>之外的所有文件。</li>
<li>花括号<code>&#123;&#125;</code> - 当你有一系列的指令，其中包含一段公共子串时，可以用花括号来自动展开这些命令。这在批量移动或转换文件时非常方便。</li>
</ul>
<figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs awk">convert image.&#123;png,jpg&#125;<br><span class="hljs-comment"># 会展开为</span><br>convert image.png image.jpg<br><br>cp <span class="hljs-regexp">/path/</span>to<span class="hljs-regexp">/project/</span>&#123;foo,bar,baz&#125;.sh /newpath<br><span class="hljs-comment"># 会展开为</span><br>cp <span class="hljs-regexp">/path/</span>to<span class="hljs-regexp">/project/</span>foo.sh <span class="hljs-regexp">/path/</span>to<span class="hljs-regexp">/project/</span>bar.sh <span class="hljs-regexp">/path/</span>to<span class="hljs-regexp">/project/</span>baz.sh /newpath<br><br><span class="hljs-comment"># 也可以结合通配使用</span><br>mv *&#123;.py,.sh&#125; folder<br><span class="hljs-comment"># 会移动所有 *.py 和 *.sh 文件</span><br><br>mkdir foo bar<br><br><span class="hljs-comment"># 下面命令会创建foo/a, foo/b, ... foo/h, bar/a, bar/b, ... bar/h这些文件</span><br>touch &#123;foo,bar&#125;/&#123;a..h&#125;<br>touch foo<span class="hljs-regexp">/x bar/y</span><br><span class="hljs-comment"># 比较文件夹 foo 和 bar 中包含文件的不同</span><br>diff &lt;(ls foo) &lt;(ls bar)<br><span class="hljs-comment"># 输出</span><br><span class="hljs-comment"># &lt; x</span><br><span class="hljs-comment"># ---</span><br><span class="hljs-comment"># &gt; y</span><br></code></pre></td></tr></table></figure>

<p>​        编写 <code>bash</code> 脚本有时候会很别扭和反直觉。例如 <a target="_blank" rel="noopener" href="https://github.com/koalaman/shellcheck">shellcheck</a>这样的工具可以帮助你定位sh/bash脚本中的错误。</p>
<p>​        注意，脚本并不一定只有用bash写才能在终端里调用。比如说，这是一段Python脚本，作用是将输入的参数倒序输出：</p>
<figure class="highlight d"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs d"><span class="hljs-meta">#!/usr/local/bin/python</span><br><span class="hljs-keyword">import</span> sys<br><span class="hljs-keyword">for</span> arg <span class="hljs-keyword">in</span> reversed(sys.argv[<span class="hljs-number">1</span>:]):<br>    print(arg)<br></code></pre></td></tr></table></figure>

<p>内核知道去用python解释器而不是shell命令来运行这段脚本，是因为脚本的开头第一行的<a target="_blank" rel="noopener" href="https://en.wikipedia.org/wiki/Shebang_(Unix)">shebang</a>。</p>
<p>​        在 <code>shebang</code> 行中使用 <a target="_blank" rel="noopener" href="https://man7.org/linux/man-pages/man1/env.1.html"><code>env</code></a> 命令是一种好的实践，它会利用环境变量中的程序来解析该脚本，这样就提高来您的脚本的可移植性。<code>env</code> 会利用我们第一节讲座中介绍过的<code>PATH</code> 环境变量来进行定位。 例如，使用了<code>env</code>的shebang看上去时这样的<code>#!/usr/bin/env python</code>。</p>
<p>shell函数和脚本有如下一些不同点：</p>
<ul>
<li>函数只能用与shell使用相同的语言，脚本可以使用任意语言。因此在脚本中包含 <code>shebang</code> 是很重要的。</li>
<li>函数仅在定义时被加载，脚本会在每次被执行时加载。这让函数的加载比脚本略快一些，但每次修改函数定义，都要重新加载一次。</li>
<li>函数会在当前的shell环境中执行，脚本会在单独的进程中执行。因此，函数可以对环境变量进行更改，比如改变当前工作目录，脚本则不行。脚本需要使用 <a href="httsp://man7.org/linux/man-pages/man1/export.1p.html"><code>export</code></a> 将环境变量导出，并将值传递给环境变量。</li>
<li>与其他程序语言一样，函数可以提高代码模块性、代码复用性并创建清晰性的结构。shell脚本中往往也会包含它们自己的函数定义。</li>
</ul>
<h2 id="Shell-工具"><a href="#Shell-工具" class="headerlink" title="Shell 工具"></a>Shell 工具</h2><h3 id="查看命令如何使用"><a href="#查看命令如何使用" class="headerlink" title="查看命令如何使用"></a>查看命令如何使用</h3><p>看到这里，您可能会有疑问，我们应该如何为特定的命令找到合适的标记呢？例如 <code>ls -l</code>, <code>mv -i</code> 和 <code>mkdir -p</code>。更普遍的是，给您一个命令行，您应该怎样了解如何使用这个命令行并找出它的不同的选项呢？ 一般来说，您可能会先去网上搜索答案，但是，UNIX 可比 StackOverflow 出现的早，因此我们的系统里其实早就包含了可以获取相关信息的方法。</p>
<p>在上一节中我们介绍过，最常用的方法是为对应的命令行添加<code>-h</code> 或 <code>--help</code> 标记。另外一个更详细的方法则是使用<code>man</code> 命令。<a target="_blank" rel="noopener" href="https://man7.org/linux/man-pages/man1/man.1.html"><code>man</code></a> 命令是手册（manual）的缩写，它提供了命令的用户手册。</p>
<p>例如，<code>man rm</code> 会输出命令 <code>rm</code> 的说明，同时还有其标记列表，包括之前我们介绍过的<code>-i</code>。 事实上，目前我们给出的所有命令的说明链接，都是网页版的Linux命令手册。即使是您安装的第三方命令，前提是开发者编写了手册并将其包含在了安装包中。在交互式的、基于字符处理的终端窗口中，一般也可以通过 <code>:help</code> 命令或键入 <code>?</code>来获取帮助。</p>
<p>有时候手册内容太过详实，让我们难以在其中查找哪些最常用的标记和语法。 <a target="_blank" rel="noopener" href="https://tldr.sh/">TLDR pages</a> 是一个很不错的替代品，它提供了一些案例，可以帮助您快速找到正确的选项。</p>
<p>例如，自己就常常在tldr上搜索<a target="_blank" rel="noopener" href="https://tldr.ostera.io/tar"><code>tar</code></a> 和 <a target="_blank" rel="noopener" href="https://tldr.ostera.io/ffmpeg"><code>ffmpeg</code></a> 的用法。</p>
<h3 id="查找文件"><a href="#查找文件" class="headerlink" title="查找文件"></a>查找文件</h3><p>程序员们面对的最常见的重复任务就是查找文件或目录。所有的类UNIX系统都包含一个名为 <a target="_blank" rel="noopener" href="https://man7.org/linux/man-pages/man1/find.1.html"><code>find</code></a>的工具，它是shell上用于查找文件的绝佳工具。<code>find</code>命令会递归地搜索符合条件的文件，例如：</p>
<figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs routeros"><span class="hljs-comment"># 查找所有名称为src的文件夹</span><br><span class="hljs-builtin-name">find</span> . -name src -type d<br><span class="hljs-comment"># 查找所有文件夹路径中包含test的python文件</span><br><span class="hljs-builtin-name">find</span> . -path <span class="hljs-string">&#x27;*/test/*.py&#x27;</span> -type f<br><span class="hljs-comment"># 查找前一天修改的所有文件</span><br><span class="hljs-builtin-name">find</span> . -mtime -1<br><span class="hljs-comment"># 查找所有大小在500k至10M的tar.gz文件</span><br><span class="hljs-builtin-name">find</span> . -size +500k -size -10M -name <span class="hljs-string">&#x27;*.tar.gz&#x27;</span><br></code></pre></td></tr></table></figure>

<p>除了列出所寻找的文件之外，find还能对所有查找到的文件进行操作。这能极大地简化一些单调的任务。</p>
<figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs routeros"><span class="hljs-comment"># 删除全部扩展名为.tmp 的文件</span><br><span class="hljs-builtin-name">find</span> . -name <span class="hljs-string">&#x27;*.tmp&#x27;</span> -exec rm &#123;&#125; \;<br><span class="hljs-comment"># 查找全部的 PNG 文件并将其转换为 JPG</span><br><span class="hljs-builtin-name">find</span> . -name <span class="hljs-string">&#x27;*.png&#x27;</span> -exec convert &#123;&#125; &#123;&#125;.jpg \;<br></code></pre></td></tr></table></figure>

<p>尽管 <code>find</code> 用途广泛，它的语法却比较难以记忆。例如，为了查找满足模式 <code>PATTERN</code> 的文件，您需要执行 <code>find -name &#39;*PATTERN*&#39;</code> (如果您希望模式匹配时是不区分大小写，可以使用<code>-iname</code>选项）</p>
<p>您当然可以使用alias设置别名来简化上述操作，但shell的哲学之一便是寻找（更好用的）替代方案。 记住，shell最好的特性就是您只是在调用程序，因此您只要找到合适的替代程序即可（甚至自己编写）。</p>
<p>例如， <a target="_blank" rel="noopener" href="https://github.com/sharkdp/fd"><code>fd</code></a> 就是一个更简单、更快速、更友好的程序，它可以用来作为<code>find</code>的替代品。它有很多不错的默认设置，例如输出着色、默认支持正则匹配、支持unicode并且我认为它的语法更符合直觉。以模式<code>PATTERN</code> 搜索的语法是 <code>fd PATTERN</code>。</p>
<p>大多数人都认为 <code>find</code> 和 <code>fd</code> 已经很好用了，但是有的人可能想知道，我们是不是可以有更高效的方法，例如不要每次都搜索文件而是通过编译索引或建立数据库的方式来实现更加快速地搜索。</p>
<p>这就要靠 <a target="_blank" rel="noopener" href="https://man7.org/linux/man-pages/man1/locate.1.html"><code>locate</code></a> 了。 <code>locate</code> 使用一个由 <a target="_blank" rel="noopener" href="https://man7.org/linux/man-pages/man1/updatedb.1.html"><code>updatedb</code></a>负责更新的数据库，在大多数系统中 <code>updatedb</code> 都会通过 <a target="_blank" rel="noopener" href="https://man7.org/linux/man-pages/man8/cron.8.html"><code>cron</code></a>每日更新。这便需要我们在速度和时效性之间作出权衡。而且，<code>find</code> 和类似的工具可以通过别的属性比如文件大小、修改时间或是权限来查找文件，<code>locate</code>则只能通过文件名。 <a target="_blank" rel="noopener" href="https://unix.stackexchange.com/questions/60205/locate-vs-find-usage-pros-and-cons-of-each-other">here</a>有一个更详细的对比。</p>
<h3 id="查找代码"><a href="#查找代码" class="headerlink" title="查找代码"></a>查找代码</h3><p>查找文件是很有用的技能，但是很多时候您的目标其实是查看文件的内容。一个最常见的场景是您希望查找具有某种模式的全部文件，并找它们的位置。</p>
<p>为了实现这一点，很多类UNIX的系统都提供了<a target="_blank" rel="noopener" href="https://man7.org/linux/man-pages/man1/grep.1.html"><code>grep</code></a>命令，它是用于对输入文本进行匹配的通用工具。它是一个非常重要的shell工具，我们会在后续的数据清理课程中深入的探讨它。</p>
<p><code>grep</code> 有很多选项，这也使它成为一个非常全能的工具。其中我经常使用的有 <code>-C</code> ：获取查找结果的上下文（Context）；<code>-v</code> 将对结果进行反选（Invert），也就是输出不匹配的结果。举例来说， <code>grep -C 5</code> 会输出匹配结果前后五行。当需要搜索大量文件的时候，使用 <code>-R</code> 会递归地进入子目录并搜索所有的文本文件。</p>
<p>但是，我们有很多办法可以对 <code>grep -R</code> 进行改进，例如使其忽略<code>.git</code> 文件夹，使用多CPU等等。</p>
<p>因此也出现了很多它的替代品，包括 <a target="_blank" rel="noopener" href="https://beyondgrep.com/">ack</a>, <a target="_blank" rel="noopener" href="https://github.com/ggreer/the_silver_searcher">ag</a> 和 <a target="_blank" rel="noopener" href="https://github.com/BurntSushi/ripgrep">rg</a>。它们都特别好用，但是功能也都差不多，我比较常用的是 ripgrep (<code>rg</code>) ，因为它速度快，而且用法非常符合直觉。例子如下：</p>
<figure class="highlight vala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs vala"><span class="hljs-meta"># 查找所有使用了 requests 库的文件</span><br>rg -t py <span class="hljs-string">&#x27;import requests&#x27;</span><br><span class="hljs-meta"># 查找所有没有写 shebang 的文件（包含隐藏文件）</span><br>rg -u --files-without-match <span class="hljs-string">&quot;^#!&quot;</span><br><span class="hljs-meta"># 查找所有的foo字符串，并打印其之后的5行</span><br>rg foo -A <span class="hljs-number">5</span><br><span class="hljs-meta"># 打印匹配的统计信息（匹配的行和文件的数量）</span><br>rg --stats PATTERN<br></code></pre></td></tr></table></figure>

<p>与 <code>find</code>/<code>fd</code> 一样，重要的是你要知道有些问题使用合适的工具就会迎刃而解，而具体选择哪个工具则不是那么重要。</p>
<h3 id="查找-shell-命令"><a href="#查找-shell-命令" class="headerlink" title="查找 shell 命令"></a>查找 shell 命令</h3><p>目前为止，我们已经学习了如何查找文件和代码，但随着你使用shell的时间越来越久，您可能想要找到之前输入过的某条命令。首先，按向上的方向键会显示你使用过的上一条命令，继续按上键则会遍历整个历史记录。</p>
<p><code>history</code> 命令允许您以程序员的方式来访问shell中输入的历史命令。这个命令会在标准输出中打印shell中的里面命令。如果我们要搜索历史记录，则可以利用管道将输出结果传递给 <code>grep</code> 进行模式搜索。 <code>history | grep find</code> 会打印包含find子串的命令。</p>
<p>对于大多数的shell来说，您可以使用 <code>Ctrl+R</code> 对命令历史记录进行回溯搜索。敲 <code>Ctrl+R</code> 后您可以输入子串来进行匹配，查找历史命令行。</p>
<p>反复按下就会在所有搜索结果中循环。在 <a target="_blank" rel="noopener" href="https://github.com/zsh-users/zsh-history-substring-search">zsh</a>中，使用方向键上或下也可以完成这项工作。</p>
<p><code>Ctrl+R</code> 可以配合 <a target="_blank" rel="noopener" href="https://github.com/junegunn/fzf/wiki/Configuring-shell-key-bindings#ctrl-r">fzf</a> 使用。<code>fzf</code> 是一个通用对模糊查找工具，它可以和很多命令一起使用。这里我们可以对历史命令进行模糊查找并将结果以赏心悦目的格式输出。</p>
<p>另外一个和历史命令相关的技巧我喜欢称之为<strong>基于历史的自动补全</strong>。 这一特性最初是由 <a target="_blank" rel="noopener" href="https://fishshell.com/">fish</a> shell 创建的，它可以根据您最近使用过的开头相同的命令，动态地对当前对shell命令进行补全。这一功能在 <a target="_blank" rel="noopener" href="https://github.com/zsh-users/zsh-autosuggestions">zsh</a> 中也可以使用，它可以极大的提高用户体验。</p>
<p>你可以修改 shell history 的行为，例如，如果在命令的开头加上一个空格，它就不会被加进shell记录中。当你输入包含密码或是其他敏感信息的命令时会用到这一特性。 为此你需要在<code>.bashrc</code>中添加<code>HISTCONTROL=ignorespace</code>或者向<code>.zshrc</code> 添加 <code>setopt HIST_IGNORE_SPACE</code>。 如果你不小心忘了在前面加空格，可以通过编辑。<code>bash_history</code>或 <code>.zhistory</code> 来手动地从历史记录中移除那一项。</p>
<h3 id="文件夹导航"><a href="#文件夹导航" class="headerlink" title="文件夹导航"></a>文件夹导航</h3><p>之前对所有操作我们都默认一个前提，即您已经位于想要执行命令的目录下，但是如何才能高效地在目录 间随意切换呢？有很多简便的方法可以做到，比如设置alias，使用 <a target="_blank" rel="noopener" href="https://man7.org/linux/man-pages/man1/ln.1.html">ln -s</a>创建符号连接等。而开发者们已经想到了很多更为精妙的解决方案。</p>
<p>由于本课程的目的是尽可能对你的日常习惯进行优化。因此，我们可以使用<a target="_blank" rel="noopener" href="https://github.com/clvv/fasd"><code>fasd</code></a>和<a target="_blank" rel="noopener" href="https://github.com/wting/autojump">autojump</a>这两个工具来查找最常用或最近使用的文件和目录。</p>
<p>Fasd 基于 <a target="_blank" rel="noopener" href="https://developer.mozilla.org/en-US/docs/Mozilla/Tech/Places/Frecency_algorithm"><em>frecency</em></a>对文件和文件排序，也就是说它会同时针对频率（<em>frequency</em> ）和时效（ <em>recency</em>）进行排序。默认情况下，<code>fasd</code>使用命令 <code>z</code> 帮助我们快速切换到最常访问的目录。例如， 如果您经常访问<code>/home/user/files/cool_project</code> 目录，那么可以直接使用 <code>z cool</code> 跳转到该目录。对于 <code>autojump</code>，则使用<code>j cool</code>代替即可。</p>
<p>还有一些更复杂的工具可以用来概览目录结构，例如 <a target="_blank" rel="noopener" href="https://linux.die.net/man/1/tree"><code>tree</code></a>, <a target="_blank" rel="noopener" href="https://github.com/Canop/broot"><code>broot</code></a> 或更加完整的文件管理器，例如 <a target="_blank" rel="noopener" href="https://github.com/jarun/nnn"><code>nnn</code></a> 或 <a target="_blank" rel="noopener" href="https://github.com/ranger/ranger"><code>ranger</code></a>。</p>
<h2 id="笔记"><a href="#笔记" class="headerlink" title="笔记"></a>笔记</h2>
            </div>
            <hr>
            <div>
              <div class="post-metas mb-3">
                
                  <div class="post-meta mr-3">
                    <i class="iconfont icon-category"></i>
                    
                      <a class="hover-with-bg" href="/categories/note/">note</a>
                    
                  </div>
                
                
                  <div class="post-meta">
                    <i class="iconfont icon-tags"></i>
                    
                      <a class="hover-with-bg" href="/tags/Linux/">Linux</a>
                    
                      <a class="hover-with-bg" href="/tags/missing-semester/">missing-semester</a>
                    
                  </div>
                
              </div>
              
                <p class="note note-warning">
                  
                    本博客所有文章除特别声明外，均采用 <a target="_blank" href="https://creativecommons.org/licenses/by-sa/4.0/deed.zh" rel="nofollow noopener noopener">CC BY-SA 4.0 协议</a> ，转载请注明出处！
                  
                </p>
              
              
                <div class="post-prevnext">
                  <article class="post-prev col-6">
                    
                    
                      <a href="/2021/11/21/MySQL%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/">
                        <i class="iconfont icon-arrowleft"></i>
                        <span class="hidden-mobile">MySQL_note</span>
                        <span class="visible-mobile">上一篇</span>
                      </a>
                    
                  </article>
                  <article class="post-next col-6">
                    
                    
                      <a href="/2021/10/26/%E8%AE%A1%E7%AE%97%E6%9C%BA%E6%95%99%E8%82%B2%E4%B8%AD%E7%BC%BA%E5%A4%B1%E7%9A%84%E4%B8%80%E8%AF%BE(%E4%B8%80)/">
                        <span class="hidden-mobile">计算机教育中缺失的一课(一)</span>
                        <span class="visible-mobile">下一篇</span>
                        <i class="iconfont icon-arrowright"></i>
                      </a>
                    
                  </article>
                </div>
              
            </div>

            
              <!-- Comments -->
              <article class="comments" id="comments" lazyload>
                
                  
                
                
  <div id="twikoo"></div>
  <script type="text/javascript">
    Fluid.utils.loadComments('#comments', function() {
      Fluid.utils.createScript('https://cdn.jsdelivr.net/npm/twikoo@1/dist/twikoo.all.min.js', function() {
        var options = Object.assign(
          {"envId":"blog-3gentarg3e6a1b5e","region":"ap-shanghai","path":"window.location.pathname"},
          {
            el: '#twikoo',
            path: 'window.location.pathname',
            onCommentLoaded: function() {
              Fluid.plugins.initFancyBox('#twikoo .tk-content img:not(.tk-owo-emotion)');
            }
          }
        )
        twikoo.init(options)
      });
    });
  </script>
  <noscript>Please enable JavaScript to view the comments</noscript>


              </article>
            
          </article>
        </div>
      </div>
    </div>
    
      <div class="d-none d-lg-block col-lg-2 toc-container" id="toc-ctn">
        <div id="toc">
  <p class="toc-header"><i class="iconfont icon-list"></i>&nbsp;目录</p>
  <div class="toc-body" id="toc-body"></div>
</div>

      </div>
    
  </div>
</div>

<!-- Custom -->


    

    
      <a id="scroll-top-button" aria-label="TOP" href="#" role="button">
        <i class="iconfont icon-arrowup" aria-hidden="true"></i>
      </a>
    

    
      <div class="modal fade" id="modalSearch" tabindex="-1" role="dialog" aria-labelledby="ModalLabel"
     aria-hidden="true">
  <div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
    <div class="modal-content">
      <div class="modal-header text-center">
        <h4 class="modal-title w-100 font-weight-bold">搜索</h4>
        <button type="button" id="local-search-close" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body mx-3">
        <div class="md-form mb-5">
          <input type="text" id="local-search-input" class="form-control validate">
          <label data-error="x" data-success="v"
                 for="local-search-input">关键词</label>
        </div>
        <div class="list-group" id="local-search-result"></div>
      </div>
    </div>
  </div>
</div>
    

    
  </main>

  <footer class="text-center mt-5 py-3">
  <div class="footer-content">
     <div class="copyright">&copy;2020 - 2021 By Kerosene.W</div> <a href="https://hexo.io" target="_blank" rel="nofollow noopener"><span>Hexo</span></a> <i class="iconfont icon-love"></i> <a href="https://github.com/fluid-dev/hexo-theme-fluid" target="_blank" rel="nofollow noopener"><span>Fluid</span></a> 
  </div>
  
  <div class="statistics">
    
    

    
      
        <!-- LeanCloud 统计PV -->
        <span id="leancloud-site-pv-container" style="display: none">
            总访问量 
            <span id="leancloud-site-pv"></span>
             次
          </span>
      
      
        <!-- LeanCloud 统计UV -->
        <span id="leancloud-site-uv-container" style="display: none">
            总访客数 
            <span id="leancloud-site-uv"></span>
             人
          </span>
      

    
  </div>


  

  
</footer>


  <!-- SCRIPTS -->
  
  <script  src="https://cdn.jsdelivr.net/npm/nprogress@0/nprogress.min.js" ></script>
  <link  rel="stylesheet" href="https://cdn.jsdelivr.net/npm/nprogress@0/nprogress.min.css" />

  <script>
    NProgress.configure({"showSpinner":false,"trickleSpeed":100})
    NProgress.start()
    window.addEventListener('load', function() {
      NProgress.done();
    })
  </script>


<script  src="https://cdn.jsdelivr.net/npm/jquery@3/dist/jquery.min.js" ></script>
<script  src="https://cdn.jsdelivr.net/npm/bootstrap@4/dist/js/bootstrap.min.js" ></script>
<script  src="/js/events.js" ></script>
<script  src="/js/plugins.js" ></script>

<!-- Plugins -->


  <script  src="/js/local-search.js" ></script>



  
    <script  src="/js/img-lazyload.js" ></script>
  



  



  
    <script  src="https://cdn.jsdelivr.net/npm/tocbot@4/dist/tocbot.min.js" ></script>
  
  
    <script  src="https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@3/dist/jquery.fancybox.min.js" ></script>
  
  
    <script  src="https://cdn.jsdelivr.net/npm/anchor-js@4/anchor.min.js" ></script>
  
  
    <script defer src="https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js" ></script>
  




  <script defer src="/js/leancloud.js" ></script>



  <script  src="https://cdn.jsdelivr.net/npm/typed.js@2/lib/typed.min.js" ></script>
  <script>
    (function (window, document) {
      var typing = Fluid.plugins.typing;
      var title = document.getElementById('subtitle').title;
      
        typing(title);
      
    })(window, document);
  </script>












  

  

  

  

  

  





<!-- 主题的启动项 保持在最底部 -->
<script  src="/js/boot.js" ></script>


</body>
</html>
